﻿using Microsoft.Extensions.Logging;
using Sgr.Exceptions;
using Sgr.Redis;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Sgr.DistributedLock
{
    public class RedisDistributedLock : IDistributedLock
    {
        private readonly string _hostName;
        private readonly IRedisDatabaseContext _redisDatabaseContext;
        private readonly ILogger _logger;

        public RedisDistributedLock(IRedisDatabaseContext redisDatabaseContext, ILogger<RedisDistributedLock> logger)
        {
            _hostName = Dns.GetHostName() + ':' + Environment.ProcessId;// Process.GetCurrentProcess().Id;
            _redisDatabaseContext = redisDatabaseContext;
            _logger = logger;
        }

        #region IDistributedLock

        public ILocker AcquireLock(string key, TimeSpan? expiration = null)
        {
            var locker = TryAcquireLock(key, TimeSpan.MaxValue, expiration) ?? throw new RedisDistributedLockException("Acquire Lock Fail!");

            return locker;
        }

        public async Task<ILocker> AcquireLockAsync(string key, TimeSpan? expiration = null)
        {
            var locker = await TryAcquireLockAsync(key, TimeSpan.MaxValue, expiration) ?? throw new RedisDistributedLockException("Acquire Lock Fail!");

            return locker;
        }

        public bool IsLockAcquired(string key)
        {
            if (string.IsNullOrEmpty(key))
                return false;

            var redisDatabase = _redisDatabaseContext.Connect();
            if (redisDatabase == null)
            {
                _logger.LogError("Fails to check whether the named lock '{LockName}' is already acquired.", getLockKey(key));
                return false;
            }

            try
            {
                return (redisDatabase.LockQuery(getLockKey(key))).HasValue;
            }
            catch (Exception e)
            {
                _logger.LogError(e, "Fails to check whether the named lock '{LockName}' is already acquired.", getLockKey(key));
            }

            return false;
        }

        private RedisKey getLockKey(string key)
        {
            // 添加防止key为空的检查
            if (string.IsNullOrEmpty(key))
                throw new ArgumentNullException(nameof(key));

            return _redisDatabaseContext.InstancePrefix
                .Append("lock:")  // 使用:作为分隔符更符合Redis命名约定
                .Append(key);
        }

        public async Task<bool> IsLockAcquiredAsync(string key)
        {
            if (string.IsNullOrEmpty(key))
                return false;

            var redisDatabase = await _redisDatabaseContext.ConnectAsync();
            if (redisDatabase == null)
            {
                _logger.LogError("Fails to check whether the named lock '{LockName}' is already acquired.", getLockKey(key));
                return false;
            }

            try
            {
                return (await redisDatabase.LockQueryAsync(getLockKey(key))).HasValue;
            }
            catch (Exception e)
            {
                _logger.LogError(e, "Fails to check whether the named lock '{LockName}' is already acquired.", getLockKey(key));
            }

            return false;
        }

        public ILocker? TryAcquireLock(string key, TimeSpan timeout, TimeSpan? expiration = null)
        {
            Check.StringNotNullOrEmpty(key, nameof(key));

            if (timeout < TimeSpan.Zero)
                throw new ArgumentException("Timeout must be greater than or equal to zero.", nameof(timeout));

            if (expiration != null && expiration < TimeSpan.Zero)
                throw new ArgumentException("Expiration must be greater than or equal to zero.", nameof(expiration));

            using (var cts = new CancellationTokenSource(timeout != TimeSpan.MaxValue ? timeout : Timeout.InfiniteTimeSpan))
            {
                var retries = 0.0;

                while (!cts.IsCancellationRequested)
                {
                    var locked = Lock(key, expiration ?? TimeSpan.MaxValue);

                    if (locked)
                    {
                        return new Locker(this, key, expiration);
                    }

                    try
                    {
                        Task.Delay(GetDelay(++retries), cts.Token);
                    }
                    catch (TaskCanceledException)
                    {
                        if (_logger.IsEnabled(LogLevel.Debug))
                        {
                            _logger.LogDebug("Timeout elapsed before acquiring the named lock '{LockName}' after the given timeout of '{Timeout}'.",
                               getLockKey(key), timeout.ToString());
                        }
                    }
                }
            }

            return null;
        }

        public async Task<ILocker?> TryAcquireLockAsync(string key, TimeSpan timeout, TimeSpan? expiration = null)
        {
            Check.StringNotNullOrEmpty(key, nameof(key));

            if (timeout < TimeSpan.Zero)
                throw new ArgumentException("Timeout must be greater than or equal to zero.", nameof(timeout));

            if (expiration != null && expiration < TimeSpan.Zero)
                throw new ArgumentException("Expiration must be greater than or equal to zero.", nameof(expiration));

            using (var cts = new CancellationTokenSource(timeout != TimeSpan.MaxValue ? timeout : Timeout.InfiniteTimeSpan))
            {
                var retries = 0.0;

                while (!cts.IsCancellationRequested)
                {
                    var locked = await LockAsync(key, expiration ?? TimeSpan.MaxValue);

                    if (locked)
                        return new Locker(this, key, expiration);

                    try
                    {
                        await Task.Delay(GetDelay(++retries), cts.Token);
                    }
                    catch (TaskCanceledException)
                    {
                        if (_logger.IsEnabled(LogLevel.Debug))
                        {
                            _logger.LogDebug("Timeout elapsed before acquiring the named lock '{LockName}' after the given timeout of '{Timeout}'.",
                               getLockKey(key), timeout.ToString());
                        }
                    }
                }
            }

            return null;
        }

        #endregion IDistributedLock

        #region Locker

        //锁续期
        //https://www.5axxw.com/questions/simple/s961px
        //https://zhuanlan.zhihu.com/p/635007911?utm_id=0

        private class Locker : ILocker
        {
            private readonly ILogger _logger;
            private readonly RedisDistributedLock _lock;
            private readonly string _key;

            private volatile bool _disposed;
            private readonly object _disposeLock = new object();

            private readonly CancellationTokenSource _cts;

            private readonly TimeSpan _expiration;

            public Locker(RedisDistributedLock redislock, string key, TimeSpan? expiration = null)
            {
                _lock = redislock ?? throw new ArgumentNullException(nameof(redislock));
                _key = key ?? throw new ArgumentNullException(nameof(key));
                _logger = redislock._logger;

                _expiration = expiration ?? TimeSpan.FromMinutes(30); // 设置默认过期时间

                // 设置合理的最大续期时间
                var maxRenewalTime = TimeSpan.FromHours(24);
                _cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(
                    Math.Min(_expiration.TotalMilliseconds * 10, maxRenewalTime.TotalMilliseconds)));

                if (_expiration > TimeSpan.Zero)
                {
                    startRenewTask(GetOptimalRenewInterval(_expiration));
                }
            }

            private static TimeSpan GetOptimalRenewInterval(TimeSpan expiration)
            {
                // 根据过期时间动态计算最优续期间隔
                var interval = expiration.TotalMilliseconds;
                var minInterval = 1000; // 最小1秒
                var maxInterval = 30000; // 最大30秒

                return TimeSpan.FromMilliseconds(Math.Clamp(interval, minInterval, maxInterval));
            }

            private void startRenewTask(TimeSpan renewInterval)
            {
                // 建议的优化实现
                var safeRenewInterval = TimeSpan.FromMilliseconds(_expiration.TotalMilliseconds * 0.7); // 在过期时间的70%处续期
                var minRenewInterval = TimeSpan.FromSeconds(1); // 设置最小续期间隔
                var actualInterval = TimeSpan.FromMilliseconds(Math.Max(
                    Math.Min(safeRenewInterval.TotalMilliseconds, renewInterval.TotalMilliseconds),
                    minRenewInterval.TotalMilliseconds));

                Task.Run(async () =>
                {
                    using PeriodicTimer timer = new(actualInterval);
                    try
                    {
                        while (!_cts.IsCancellationRequested && !_disposed &&
                               await timer.WaitForNextTickAsync(_cts.Token))
                        {
                            await _lock.LockExtendAsync(_key, _expiration);
                        }
                    }
                    catch (OperationCanceledException)
                    {
                        // 正常取消，忽略异常
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError(ex, "Lock renewal task failed for key: {key}", _key);
                    }
                }, _cts.Token);

                //.ContinueWith((task) =>
                //{
                //});
            }

            public async ValueTask DisposeAsync()
            {
                if (!_disposed) // 快速检查避免获取锁
                {
                    lock (_disposeLock)
                    {
                        if (_disposed) return;
                        _disposed = true;
                    }

                    try
                    {
                        _cts?.Cancel();             // 确保取消所有续期任务
                        await Task.Delay(50);       // 50ms 通常足够任务响应取消信号
                        _cts?.Dispose();
                        await _lock.ReleaseAsync(_key).ConfigureAwait(false);
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError(ex, "Error during async disposal of lock: {key}", _key);
                        throw;
                    }
                }
            }

            public void Dispose()
            {
                if (!_disposed) // 快速检查避免获取锁
                {
                    lock (_disposeLock)
                    {
                        if (_disposed) return;
                        _disposed = true;
                    }

                    try
                    {
                        _cts?.Cancel(); // 确保取消所有续期任务
                        Thread.Sleep(50); // 50ms 通常足够任务响应取消信号
                        _cts?.Dispose();
                        _lock.Release(_key);
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError(ex, "Error during disposal of lock: {key}", _key);
                        throw;
                    }
                }
            }
        }

        #endregion Locker

        private bool Lock(string key, TimeSpan expiry)
        {
            var redisDatabase = _redisDatabaseContext.Connect();
            if (redisDatabase == null)
            {
                _logger.LogError("Fails to acquire the named lock '{LockName}'.", getLockKey(key));
                return false;
            }

            try
            {
                return redisDatabase.LockTake(getLockKey(key), _hostName, expiry);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Fails to acquire the named lock '{LockName}'.", getLockKey(key));
                _redisDatabaseContext.OnRedisError(ex, redisDatabase);
            }

            return false;
        }

        private async Task<bool> LockAsync(string key, TimeSpan expiry)
        {
            var redisDatabase = await _redisDatabaseContext.ConnectAsync();
            if (redisDatabase == null)
            {
                _logger.LogError("Fails to acquire the named lock '{LockName}'.", getLockKey(key));
                return false;
            }

            try
            {
                return await redisDatabase.LockTakeAsync(getLockKey(key), _hostName, expiry);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Fails to acquire the named lock '{LockName}'.", getLockKey(key));
                _redisDatabaseContext.OnRedisError(ex, redisDatabase);
            }

            return false;
        }

        private async ValueTask ReleaseAsync(string key)
        {
            var redisDatabase = await _redisDatabaseContext.ConnectAsync();
            try
            {
                await redisDatabase.LockReleaseAsync(getLockKey(key), _hostName);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Fails to release the named lock '{LockName}'.", getLockKey(key));
                _redisDatabaseContext.OnRedisError(ex, redisDatabase);
            }
        }

        private void Release(string key)
        {
            var redisDatabase = _redisDatabaseContext.Connect();
            try
            {
                redisDatabase.LockRelease(getLockKey(key), _hostName, CommandFlags.FireAndForget);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Fails to release the named lock '{LockName}'.", getLockKey(key));
                _redisDatabaseContext.OnRedisError(ex, redisDatabase);
            }
        }

        private async Task LockExtendAsync(string key, TimeSpan expiry)
        {
            var redisDatabase = await _redisDatabaseContext.ConnectAsync();
            try
            {
                await redisDatabase.LockExtendAsync(getLockKey(key), _hostName, expiry);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Fails to extend the named lock '{LockName}'.", getLockKey(key));
                _redisDatabaseContext.OnRedisError(ex, redisDatabase);
            }
        }

        private static readonly double _baseDelay = 100;
        private static readonly double _maxDelay = 10000;

        private static TimeSpan GetDelay(double retries)
        {
            var delay = _baseDelay
                * (1.0 + ((Math.Pow(1.8, retries - 1.0) - 1.0)
                    * (0.6 + new Random().NextDouble() * 0.4)));

            return TimeSpan.FromMilliseconds(Math.Min(delay, _maxDelay));

            // 2 examples with 10 retries
            // --------------------------
            // 100     100 (start from base)
            // 164     171
            // 256     312
            // 401     519
            // 754     766
            // 1327    1562
            // 2950    3257
            // 4596    4966
            // 7215    8667
            // 10000   10000 (max reached)
        }
    }
}